38. 使用接口模拟可扩展的枚举

      也就是说,对于可扩展枚举类型至少有一个有说服力的用例,这就是操作码(operation codes),也称为 opcodes。 操作码是枚举类型,其元素表示某些机器上的操作,例如条目 34 中的 类型,它表示简单计算器上的功能。 有时需要让 API 的用户提供他们自己的操作,从而有效地扩展 API 提供的操作集。

      幸运的是,使用枚举类型有一个很好的方法来实现这种效果。基本思想是利用枚举类型可以通过为 opcode 类型定义一个接口,并实现任意接口。例如,这里是来自条目 34 的 Operation 类型的可扩展版本:

      虽然枚举类型(BasicOperation)不可扩展,但接口类型(Operation)是可以扩展的,并且它是用于表示 API 中的操作的接口类型。 你可以定义另一个实现此接口的枚举类型,并使用此新类型的实例来代替基本类型。 例如,假设想要定义前面所示的操作类型的扩展,包括指数运算和余数运算。 你所要做的就是编写一个实现 Operation 接口的枚举类型:

      不仅可以在任何需要「基本枚举」的地方传递「扩展枚举」的单个实例,而且还可以传入整个扩展枚举类型,并使用其元素。 例如,这里是第 163 页上的一个测试程序版本,它执行之前定义的所有扩展操作:

      注意,扩展的操作类型的类字面文字(ExtendedOperation.class)从 main 方法里传递给了 test 方法,用来描述扩展操作的集合。这个类的字面文字用作限定的类型令牌(详见第 33 条)。opEnumType 参数中复杂的声明(<T extends Enum<T> & Operation> Class<T>)确保了 Class 对象既是枚举又是 Operation 的子类,这正是遍历元素和执行每个元素相关联的操作时所需要的。

      第二种方式是传递一个 Collection<? extends Operation>,这是一个限定通配符类型(详见第 31 条),而不是传递了一个 class 对象:

      上面的两个程序在运行命令行输入参数 4 和 2 时生成以下输出:

      使用接口来模拟可扩展枚举的一个小缺点是,实现不能从一个枚举类型继承到另一个枚举类型。如果实现代码不依赖于任何状态,则可以使用默认实现(详见第 20 条)将其放置在接口中。在我们的 Operation 示例中,存储和检索与操作关联的符号的逻辑必须在 BasicOperationExtendedOperation 中重复。在这种情况下,这并不重要,因为很少的代码是冗余的。如果有更多的共享功能,可以将其封装在辅助类或静态辅助方法中,以消除代码冗余。

      该条目中描述的模式在 Java 类库中有所使用。例如,java.nio.file.LinkOption 枚举类型实现了 CopyOptionOpenOption 接口。